Repository navigation
Skills registry: pull, bind and qualify attested skills - #1623
Merged
Merged
Conversation
A registry of compiled skills. pull verifies a keyless Sigstore attestation from the registry release workflow, naming the exact index and archives; refuses an expired index and one older than the newest seen from the same attesting identity at any address; and marks the pulled bundle so run, replay and emit refuse it until skill qualify passes on the user's own instance.
abrichr
enabled auto-merge (squash)
October 29, 2026 00:44
| file=sys.stderr, | ||
| ) | ||
| return 1 | ||
| print(f"Sigstore trusted root refreshed: {path}") |
… 2.x in the test The check job installed the engine without hashes; it now uses locked_install.py like every other job. The shipped Sigstore trusted root is added to the reviewed public artifact inventory. The emitted MCP server test stands in for FastMCP, which mcp 2.x renamed, as test_emit does.
abrichr
added a commit
that referenced
this pull request
Oct 29, 2026
This was referenced Oct 29, 2026
abrichr
added a commit
that referenced
this pull request
Oct 29, 2026
…guards back on the PR lane (#1632) Main's Packages run fails tests/test_cli_hints.py::test_printed_hints_never_hard_code_the_engine_spelling on registry.py's pull hint (#1623), which printed a literal "openadapt-flow skill ...". Under the launcher that names a program the person never typed; it now goes through cli_hints.command() like every other hint. #1623's own CI never ran the test: #1608 moved it to nightly as a 6 s test, though it is a source scan that guards every printed hint. It returns to the pull-request lane, with five safety guards the same rule moved by mistake: identity (a numeric MRN homonym never verifies), durable resume (never resends), the safety gallery (every dangerous case is safe), halt detail (a typed value never appears) and self-repair (a healthy run never calls the agent). Together they add about 90 test-seconds across the four shards.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces #1592 with one squashed commit, so the secret scan, which reads the PR's history, no longer sees two test values from its earlier commits. The content is #1592's final tree (e8a161b).
Review history:
Since that reply:
sigstore(theregistryanddevextras) is limited to Python 3.11+, because on 3.10 it resolves dnspython 2.8.0 (GHSA-45c3-73f7-pv4m). Two test values that looked like credentials were replaced with plain text.Stage 1 of the verified-skills registry. The design is in
packages/flow/docs/design/SKILLS_REGISTRY.md, and it builds onopenadapt pull(#1173).A skill pulled from the registry runs only after it is qualified on the user's own instance: a registry certification is evidence about our test copy, never about theirs. Who published the index is established keylessly, with GitHub artifact attestations, as for our other releases.
Who published it: keyless attestation
The release.
.github/workflows/release-registry.ymlruns in stages:registry-<suffix>with the release App.checkjob runs the bundle checks.attestjob then runs inrelease-identity, and it is the only job withid-token: write. It builds reproducible archives, stamps the index (issued_at, a 30-dayexpires_at, andserial) and attestsindex.jsonand every archive withactions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6. It checks its own attestation with the pull verifier, using only hash-pinned wheels (registry/verify-requirements.txt).publishreleases the archives,index.jsonandindex.sigstore.json. It opens a PR with the index and its attestation, adding both by name.The check.
pullverifiesindex.sigstore.jsonwith thesigstorepackage (the newregistryextra, so the base install doesn't grow). The check is offline againstopenadapt_flow/registry_trusted_root.json, for a pinned identity:https://token.actions.githubusercontent.com;https://github.com/OpenAdaptAI/OpenAdapt/.github/workflows/release-registry.yml@refs/tags/registry-*;https://github.com/OpenAdaptAI/OpenAdapt.The statement must name the sha256 of the exact index that was read, and of any archive pulled.
Refreshing trust.
pull --refresh-trustfetches the current Sigstore trusted root through Sigstore's TUF repository into$OPENADAPT_HOME. After that, verification uses the refreshed root.Rollback and freeze. The attested index carries
issued_at,expires_atandserial.pullrefuses an expired index, one issued in the future, and a serial lower than the highest it has seen from that address ($OPENADAPT_HOME/registry-state.json). An entry dropped from the index changes its digest.Local only. For an index being authored on the local disk:
--allow-unsignedtakes one with no attestation;--allow-test-keytakes one attested by the local test signer (registry_attest.sign_test).Both flags are refused for a remote index, and plain http is refused.
Removed:
REGISTRY_SIGNING_SEED) and theregistry-signingenvironment;registry_keys.json;registry_index_sha256Rekor anchor (the attestation is already in Rekor).Pull, bind, qualify, run
pullstoresWorkflow.registry: the entry id, version, index, the entry's sha256, who attested it and the published digest. The content digest covers it.pullalso records the program's steps outside the bundle ($OPENADAPT_HOME/pulled.json), so a bundle with its provenance stripped is still refused. It writesskill.lock.jsonand expires any certification the bundle carries.skill bindrecords the user's values for the contract's declared variables: URLs, environment variable names, local paths and local commands. Each is checked against its kind, and a credential is only ever a variable name. The binding file is written mode 600.skill qualifyruns the qualification cases in place against the bound instance. It uses the private tier's full check whenOPENADAPT_REGISTRY_TOKENis set, else the bundled minimal one.${VAR}. Hooks and plugins are refused, and expansion uses the binding alone.--verifycounts only for a skill that ships none, or with--replace-contract.run,replay,emit-skillandemit-mcprefuse a pulled skill without a current certification on a named instance, recorded byskill qualifyfor this digest.runandreplaycheck the deployment's app address. An emitted web MCP server checks every call's URL against the qualified instance.full_contracttakes exactly a url and a sha256. The token is sent only over https to a host inregistry.PRIVATE_TIER_HOSTS, checked before it is sent. The body is size-capped and kept beside the bundle, never in it.NOTICE.screens.data: syntheticattestation. Versions order as semver.Not in this PR
mainhas noindex.sigstore.json, sopullfrom the default index is refused. A remote index has no bypass. This needs no key, only a dispatch of the reviewed workflow after this merges.Tests
tests/test_skills_registry.pyandtests/test_registry.py: the index, pull, bind, qualify and the refusal; the attestation (pull and record the attester; no attestation; the test signer needs the flag; a changed index, signature or subject); and archives.tests/test_skills_registry_review.py: one or more tests per supply-chain review item, plus the Sigstore path.The real Sigstore path. The fixture is an attestation GitHub made for the OpenAdapt Desktop 0.24.1
SHA256SUMSrelease asset (desktop-release.yml). It is verified offline with the pinned trusted root and must name that file's sha256. These are refused:These tests skip when
sigstoreisn't installed, and run in CI, becausedevincludes it.Run locally at 4c584fd: 142 passed with
sigstoreinstalled. Without it, 50 passed and the 4 real-path tests skipped. The workflow-policy, packaging and capabilities tests pass, and ruff, ruff format, mypy and actionlint are clean.🤖 Generated with Claude Code